import * as THREE from "../../../modules/three/three.module.js";
import PluginBase from "./PluginBase.js";
import {OBB} from "../../../modules/three/jsm/math/OBB.js";
import {bufferAttributeToVectors, flatten} from "../lib/util/StaticMethods.js";
import {ConvexGeometry} from "../../../modules/three/jsm/geometries/ConvexGeometry.js";
import DirPointIndicator from "../lib/gadget/DirPointIndicator.js";
import DirPointVO from "../model/DirPointVO.js";
import {MouseButton, unitZ} from "../lib/CommonConsts.js";
import {arrAsubB, removeArrayItem} from "../lib/util/ToolMethods.js";

const cubeVertexes = []
const pickedPoints = []
const mtlConvex = new THREE.MeshBasicMaterial({wireframe: true, color: '#e86833'})

// 初始化姿态辅助网格
let localFrameMesh;
function initLocalFrameMesh() {
  const cubeHalfSize = 0.5;
  const cubeSize = cubeHalfSize * 2;
  const cubeGeo = new THREE.BoxBufferGeometry(cubeSize, cubeSize, cubeSize);
  const cubeMat = new THREE.MeshLambertMaterial({
    color: '#00006b',
    wireframe: true,
  });
  const cubeMatTransparent = new THREE.MeshBasicMaterial({
    color: '#ffbc11',
    transparent: true,
    opacity: 0.5,
  });
  localFrameMesh = new THREE.Mesh(cubeGeo, cubeMat);
  const cubeMesh = new THREE.Mesh(cubeGeo, cubeMatTransparent);
  localFrameMesh.add(cubeMesh);
}

const _line3 = new THREE.Line3()
const _p1 = new THREE.Vector3()
const mtlTri = new THREE.MeshBasicMaterial({color: '#fb00fb', wireframe: true, side: THREE.DoubleSide, transparent: true})
class TriangleMesh extends THREE.Mesh {
  /**
   * @type {THREE.Plane[]}
   */
  planes = []

  constructor() {
    super();
  }
  initPlanes() {
    const {a, b, c} = this
    const arr = [a, b, c]
    const [pa, pb, pc] = arr.map(it => it.vo.positionGrid)
    const po = pa.clone().add(pb).add(pc).divideScalar(3)
    const oa = pa.clone().sub(po).normalize()
    const ob = pb.clone().sub(po).normalize()
    const oc = pc.clone().sub(po).normalize()
    const triNormal = oa.clone().cross(ob).normalize()
    const oAn = triNormal.clone().cross(oa).normalize()
    const planeOab = new THREE.Plane().setFromNormalAndCoplanarPoint(oAn, po)
    if (planeOab.distanceToPoint(pb) < 0) {
      planeOab.negate()
    }
    const oBn = triNormal.clone().cross(ob).normalize()
    const planeObc = new THREE.Plane().setFromNormalAndCoplanarPoint(oBn, po)
    if (planeObc.distanceToPoint(pc) < 0) {
      planeObc.negate()
    }
    const oCn = triNormal.clone().cross(oc).normalize()
    const planeOca = new THREE.Plane().setFromNormalAndCoplanarPoint(oCn, po)
    if (planeOca.distanceToPoint(pa) < 0) {
      planeOca.negate()
    }
    this.planes = [planeOab, planeObc, planeOca]
  }
  /**
   * @param {DirPointIndicator} a
   * @param {DirPointIndicator} b
   * @param {DirPointIndicator} c
   */
  initShape(a, b, c) {
    const arr = [a, b, c].map(it => it.vo.positionGrid)
    const posAttr = new THREE.BufferAttribute(flatten(arr), 3)
    const geometry = new THREE.BufferGeometry()
    geometry.setAttribute('position', posAttr)
    this.geometry = geometry
    this.material = mtlTri.clone()
    this.a = a
    this.b = b
    this.c = c
    return this
  }
  updateShape(a, b, c) {
    const pa = a.vo.positionGrid
    const pb = b.vo.positionGrid
    const pc = c.vo.positionGrid
    const posAttr = this.geometry.getAttribute('position')
    posAttr.setXYZ(0, pa.x, pa.y, pa.z)
    posAttr.setXYZ(1, pb.x, pb.y, pb.z)
    posAttr.setXYZ(2, pc.x, pc.y, pc.z)
    posAttr.needsUpdate = true
  }
  // 是否包含该顶点组合
  hasPointsCombo(p0, p1, p2) {
    const arr = [this.a, this.b, this.c]
    const has0 = arr.includes(p0)
    const has1 = arr.includes(p1)
    const has2 = arr.includes(p2)
    return has0 && has1 && has2
  }
  hasPoint(point) {
    return this.a === point || this.b === point || this.c === point
  }
  toggleSelect(selected) {
    this.material.wireframe = !selected
    this.material.opacity = selected ? 0.4 : 1
  }
  getAdhereEdge(positionGrid) {
    if (!this.planes.length) {
      return []
    }
    // const [planeOab, planeObc, planeOca] = this.planes
    const [dAb, dBc, dCa] = this.planes.map(plane => plane.distanceToPoint(positionGrid))
    const dists = [dAb, dBc, dCa]
    if (dists.includes(0)) {
      return []
    }
    const {a, b, c} = this
    if (dAb > 0 && dBc < 0) {
      return [a, b]
    } else if (dBc > 0 && dCa < 0) {
      return [b, c]
    } else {
      return [c, a]
    }
  }
}
const mtlSeg = new THREE.LineBasicMaterial({color: '#009d32'})
function createSegment() {
  const posAttr = new THREE.BufferAttribute(new Float32Array([0,0,0, 0,0,1]), 3)
  const lineGeo = new THREE.BufferGeometry();
  lineGeo.setAttribute('position', posAttr)
  const segment = new THREE.LineSegments(lineGeo, mtlSeg)
  return segment
}
const v1 = new THREE.Vector3()
/**
 * @param {THREE.LineSegments} segment
 * @param {DirPointIndicator} a
 * @param {DirPointIndicator} b
 */
function updateSegment(segment, a, b) {
  segment.position.copy(a.vo.positionGrid)
  v1.copy(b.vo.positionGrid).sub(a.vo.positionGrid)
  segment.scale.z = v1.length()
  segment.quaternion.setFromUnitVectors(unitZ, v1.normalize())
}
const segmentA = createSegment()
const segmentB = createSegment()
const segmentC = createSegment()

const adherePointRadius = 0.25
const _sphere = new THREE.Sphere(new THREE.Vector3(), adherePointRadius)

export default class SurfaceGridPlugin extends PluginBase {
  /**
   * @type {World3d}
   */
  world3d
  /**
   * @type {THREE.Mesh|null}
   */
  convexMesh
  /**
   * @type {TriangleMesh[]}
   */
  _triangles = []
  /**
   * @type {TriangleMesh|null}
   */
  _selectTriangle
  /**
   * @type {DirPointIndicator[]}
   */
  _dirPoints = []
  /**
   * @type {DirPointIndicator[]}
   */
  _triPoints = []
  /**
   * @type {TriangleMesh|null}
   */
  _tmpTriMesh
  /**
   * @type {DirPointIndicator|null}
   */
  _newDirPoint
  /**
   * @type {DirPointIndicator|null}
   */
  _adheringPoint

  constructor(world3d) {
    super()
    this.world3d = world3d
    initLocalFrameMesh()
  }
  startSampleCube() {
    localFrameMesh.rotation.set(0, 0, 0);
    pickedPoints.length = 0;
  }
  // 添加一个属于矩形采样空间的顶点
  _addCubeVertexHelper(intersectPoint) {
    const sphereGeo = new THREE.SphereGeometry(0.04);
    const sphereMat = new THREE.MeshBasicMaterial({
      color: '#0b0',
      wireframe: true,
    });
    const sphere = new THREE.Mesh(sphereGeo, sphereMat);
    this.world3d.scene.add(sphere);
    cubeVertexes.push(sphere);
    sphere.position.copy(intersectPoint);
    console.log('picking: ', intersectPoint.toArray());
    pickedPoints.push(intersectPoint);
    if (cubeVertexes.length >= 5) {
      console.log('picked: ', pickedPoints.map(it => it.toArray()));
      // 已经添加了五个矩形采样空间的顶点，可以绘制出一个长方体 代表采样空间
      const points = cubeVertexes.map(it => it.position);
      const box3 = new THREE.Box3().setFromPoints(points);
      box3.expandByVector(new THREE.Vector3(0.3, 0.3, 0));
      const size = box3.getSize(new THREE.Vector3());
      const center = box3.getCenter(new THREE.Vector3());
      localFrameMesh.scale.set(size.x, size.y, size.z);
      localFrameMesh.position.copy(center);
      this.world3d.scene.add(localFrameMesh);
      // 把3d 位移 旋转 缩放工具挂到 长方体上
      this.world3d.transformRotateControl.attach(localFrameMesh);
      this.world3d.store.freeState();
      cubeVertexes.forEach((it) => it.removeFromParent());
      cubeVertexes.length = 0;
    }
  }
  /**
   * 长方体采样空间设置完成后，根据空间内的顶点 寻找一个三维凸包网格
   * @return {boolean} true 表示三维凸包网格创建成功
   */
  sampleOutConvexMesh() {
    if (!localFrameMesh.parent) {
      return false;
    }
    localFrameMesh.geometry.computeBoundingBox();
    const sampleObb = new OBB().fromBox3(localFrameMesh.geometry.boundingBox);
    sampleObb.applyMatrix4(localFrameMesh.matrixWorld);
    let sampleBox3 = localFrameMesh.geometry.boundingBox.clone();
    sampleBox3.applyMatrix4(localFrameMesh.matrixWorld);
    const involvedTileMeshes = []; // 保存若干个与采样空间有交集的 b3dms 瓦片网格
    this.world3d.tiles.group.traverse((obj3d) => {
      if (obj3d instanceof THREE.Mesh && obj3d.visible) {
        const tileBox3 = obj3d.geometry.boundingBox.clone();
        tileBox3.applyMatrix4(obj3d.matrixWorld);
        if (sampleBox3.intersectsBox(tileBox3)) {
          involvedTileMeshes.push(obj3d);
        }
      }
    });
    // console.log('involvedTileMeshes: ', involvedTileMeshes)
    const involvedVectors = [];
    involvedTileMeshes.forEach((mesh) => {
      const vectors = bufferAttributeToVectors(
        mesh.geometry.attributes.position,
      );
      vectors.forEach((vector) => {
        vector.applyMatrix4(mesh.matrixWorld);
        if (sampleObb.containsPoint(vector)) {
          involvedVectors.push(vector);
        }
      });
    });
    if (involvedVectors.length < 3) {
      console.error('采样空间没有包含足够的顶点，无法采样出三维凸包网格，', involvedVectors.length,);
      this.removeSampleCube();
      return false;
    }
    console.log('sampling3dConvex, involvedVectors ', involvedVectors.length)
    const convexHull = new ConvexGeometry(involvedVectors)
    const convexMesh = new THREE.Mesh(convexHull, mtlConvex)
    this.world3d.scene.add(convexMesh)
    this.removeSampleCube();
    if (this.convexMesh) {
      this.convexMesh.removeFromParent()
    }
    this.convexMesh = convexMesh
    return true;
  }
  expandPhi(start, end, cssColor, reset = false) {
    if (reset) {
      this._dirPoints.forEach(it => it.toggleSegPhiVisible(false))
    }
    const arr = this._dirPoints.filter(it => it.vo.phi >= start && it.vo.phi < end)
    arr.forEach(it => it.colorPhi(cssColor))
  }
  expandTheta(start, end, cssColor, reset = false) {
    if (reset) {
      this._dirPoints.forEach(it => it.toggleSegThetaVisible(false))
    }
    const arr = this._dirPoints.filter(it => it.vo.theta >= start && it.vo.theta < end)
    arr.forEach(it => it.colorTheta(cssColor))
  }
  storageSave(obj) {
    const dirPoints = []
    this._dirPoints.forEach(it => it.vo.toJsonObj(dirPoints))
    const triIndices = []
    this._triangles.forEach(triMesh => {
      const {a, b, c} = triMesh
      const indices = [a, b, c].map(it => this._dirPoints.indexOf(it))
      triIndices.push(...indices)
    })
    obj.surfaceGrid = {dirPoints, triIndices}
    if (this.convexMesh) {
      const arr0 = this.convexMesh.geometry.getAttribute('position').array
      const arr = []
      arr0.forEach(it => {
        arr.push(parseFloat(it.toFixed(3)))
      })
      obj.surfaceGrid.convex = arr
    }
  }
  storageLoad(jsonObj) {
    if (!jsonObj.surfaceGrid) {
      return
    }
    const scene = this.world3d.scene
    const {dirPoints, triIndices} = jsonObj.surfaceGrid
    for (let i = 0; i < dirPoints.length; i += 5) {
      const arr = dirPoints.slice(i, i + 5)
      const dirPoint = new DirPointVO().fromJsonObj(arr)
      const indicator = new DirPointIndicator(dirPoint)
      scene.add(indicator)
      this._dirPoints.push(indicator)
    }
    for (let i = 0; i < triIndices.length; i += 3) {
      const arr = triIndices.slice(i, i + 3)
      const indicators = arr.map(it => this._dirPoints[it])
      const triMesh = new TriangleMesh().initShape(...indicators)
      scene.add(triMesh)
      this._triangles.push(triMesh)
    }
    if (jsonObj.surfaceGrid.convex) {
      const posAttr = new THREE.BufferAttribute(new Float32Array(jsonObj.surfaceGrid.convex), 3)
      const convexHull = new THREE.BufferGeometry()
      convexHull.setAttribute('position', posAttr)
      convexHull.computeVertexNormals()
      const convexMesh = new THREE.Mesh(convexHull, mtlConvex)
      this.world3d.scene.add(convexMesh)
      this.convexMesh = convexMesh
    }
  }
  // 删除采样长方体
  removeSampleCube() {
    this.world3d.detachObject()
    localFrameMesh.removeFromParent()
  }
  toggleConvexMeshVisible(visible) {
    if (!this.convexMesh) {
      return
    }
    this.convexMesh.visible = visible
  }
  /**
   * 有辅助网格时，优先和辅助网格做射线碰撞
   * @param {THREE.Raycaster} raycaster
   * @return {{point: THREE.Vector3, face:{normal: THREE.Vector3}}[]}
   * @private
   */
  _getIntersects(raycaster) {
    let tileIntersects = []
    if (this.convexMesh) {
      tileIntersects = raycaster.intersectObject(this.convexMesh, false)
    }
    if (!tileIntersects.length) {
      tileIntersects = raycaster.intersectObject(this.world3d.tiles.group, true)
    }
    return tileIntersects
  }
  removeTriangleMesh() {
    if (!this.world3d.store.stateFree || !this._selectTriangle) {
      return
    }
    this._selectTriangle.removeFromParent()
    removeArrayItem(this._triangles, this._selectTriangle)
    const {a, b, c} = this._selectTriangle
    this._selectTriangle = null
    const pointsRemovable = []
    const arr = [a, b, c]
    arr.forEach(point => {
      if (!this._triangles.find(triMesh => triMesh.hasPoint(point) ) ) {
        pointsRemovable.push(point)
      }
    })
    arrAsubB(this._dirPoints, pointsRemovable)
    pointsRemovable.forEach(it => it.removeFromParent())
  }
  _appendTriangle(triPoints) {
    const tri = new TriangleMesh().initShape(...triPoints)
    this.world3d.scene.add(tri)
    this._triPoints.length = 0
    segmentA.removeFromParent()
    segmentB.removeFromParent()
    segmentC.removeFromParent()
    if (this._selectTriangle) {
      this._selectTriangle.toggleSelect(false)
    }
    this._triangles.push(tri)
    tri.toggleSelect(true)
    this._selectTriangle = tri
    tri.initPlanes()
  }
  dropdownDirPoint(event) {
    if (event.button === MouseButton.right) {
      this._newDirPoint.removeFromParent()
      this._newDirPoint = null
    } else {
      if (this._adheringPoint) {
        this._triPoints.push(this._adheringPoint)
        if (this._triPoints.length < 3) {
          return
        }
        const triExisted = this._triangles.find(it => it.hasPointsCombo(...this._triPoints))
        if (triExisted) {
          console.log('不能在该位置新增三角形')
          return // 放置顶点失败
        }
        this._appendTriangle(this._triPoints)
      } else {
        this._dirPoints.push(this._newDirPoint)
        this._triPoints.push(this._newDirPoint)
        if (this._triPoints.length === 3) {
          this._appendTriangle(this._triPoints)
        }
        this._newDirPoint = null
      }
    }

  }
  pointerMove(event) {
    const world3d = this.world3d
    const store = this.world3d.store
    const raycaster = world3d.mouseRaycaster
    if (store.stateAddingGridVert && !this._newDirPoint) {
      const indicator = new DirPointIndicator(new DirPointVO())
      world3d.scene.add(indicator)
      this._newDirPoint = indicator
    }
    if (!this._newDirPoint && !store.stateAddingGridVert) {
      return
    }
    const adheredPoints = []
    const adheredSpans = []
    this._dirPoints.forEach(dirPoint => {
      if (this._triPoints.includes(dirPoint)) {
        return
      }
      // 与已经存在的顶点进行吸附判断
      _sphere.center.copy(dirPoint.vo.positionGrid)
      const intersected = raycaster.ray.intersectsSphere(_sphere)
      if (intersected) {
        adheredPoints.push(dirPoint)
        adheredSpans.push(raycaster.ray.distanceSqToPoint(_sphere.center))
      }
    })
    if (this._adheringPoint) {
      this._adheringPoint.toggleAdhered(false)
      this._adheringPoint = null
    }
    if (adheredPoints.length) {
      const minDist = Math.min(...adheredSpans)
      const minIndex = adheredSpans.indexOf(minDist)
      this._adheringPoint = adheredPoints[minIndex]
      this._adheringPoint.toggleAdhered(true)
      this._newDirPoint.visible = false
    } else {
      this._newDirPoint.visible = true
      const intersects = this._getIntersects(raycaster)
      if (!intersects.length) {
        return
      }
      const vo = this._newDirPoint.vo
      vo.position.copy(intersects[0].point)
      vo.setNormal(intersects[0].face.normal)
      this._newDirPoint.syncByVO()
      if (this._selectTriangle) {
        const edgePoints = this._selectTriangle.getAdhereEdge(vo.positionGrid)
        if (edgePoints.length) {
          // 与选中三角形的三条边中的某个最为接近
          this._triPoints = edgePoints
        }
      }
    }

    const arr = this._triPoints
    const dirPoint = this._adheringPoint || this._newDirPoint
    if (arr.length === 1) {
      world3d.scene.add(segmentA)
      updateSegment(segmentA, dirPoint, arr[0])
    } else if (arr.length === 2) {
      world3d.scene.add(segmentB, segmentC)
      updateSegment(segmentB, dirPoint, arr[0])
      updateSegment(segmentC, dirPoint, arr[1])
      if (this._triangles.length) {
        if (!this._tmpTriMesh) {
          this._tmpTriMesh = new TriangleMesh().initShape(dirPoint, arr[0], arr[1])
          this._tmpTriMesh.toggleSelect(true)
          this._tmpTriMesh.material.color.setStyle('#8c6621')
          world3d.scene.add(this._tmpTriMesh)
        } else {
          this._tmpTriMesh.updateShape(dirPoint, arr[0], arr[1])
        }
      }
    }
  }
  pointerUpUnmoved(event) {
    const world3d = this.world3d
    const store = this.world3d.store
    const raycaster = world3d.mouseRaycaster
    if (store.stateAddingConvexVert) {
      const results = raycaster.intersectObject(world3d.tiles.group, true);
      if (results.length) {
        this._addCubeVertexHelper(results[0].point);
      }
      return;
    }
    if (store.stateAddingGridVert) {
      this.dropdownDirPoint(event)
    }
    if (event.button === MouseButton.right) {
      if (store.stateAddingGridVert) {
        const arr = [segmentA, segmentB, segmentC]
        arr.forEach(it => it.removeFromParent())
        this._triPoints.length = 0
        if (this._tmpTriMesh) {
          this._tmpTriMesh.removeFromParent()
          this._tmpTriMesh = null
        }
        store.freeState()
      }
      return
    }
    if (!store.stateFree) {
      return
    }
    if (this._selectTriangle) {
      this._selectTriangle.toggleSelect(false)
      this._selectTriangle = null
    }
    const itemIntersects = raycaster.intersectObjects(this._triangles, false)
    if (itemIntersects.length) {
      this._selectTriangle = itemIntersects[0].object
      this._selectTriangle.initPlanes()
      this._selectTriangle.toggleSelect(true)
    }
  }
}
